﻿using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Text;
using Nemerle.Utility;
using Nemerle.Compiler.Parsetree;

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using NRails;
using NRails.Database.Schema;
using BLToolkit.Mapping;
using BLToolkit.DataAccess;

namespace NRails.Macros.Model
{
    internal module ModelImpl
    {
      public GenerateColumns(t : TypeBuilder) : void
      {
          def engine = t.Manager.GetNRailsEngine();
          try 
          {
            def table = engine.GetTable(t.Name);
            
            when (table == null)
            {
               def msg = $"table $(t.Name) not found, env: '$(engine.Cfg.Env)'";
               throw Exception(msg);
            }
            
            t.GetModifiers().AddCustomAttribute(<[  BLToolkit.DataAccess.TableName($(table.Name : string)) ]>);

            def hintBuilder = StringBuilder();

            def existingProperties = t.GetProperties().Map(_.Name);
            
            def pkeys = match (table.KeyPrimary())
            {
                | null => [];
                | x => x.Columns.Split(',').Map(_.Trim());
            }
            
            foreach (col : TableColumnSchema in table.Columns)
            {
                // todo : проверка должна идти по атрибутам тулкита, 
                // а не по именам свойств
                unless (existingProperties.Any(p => p == col.Name))
                {
                    def nemerleType = ConvertToNemerleType(t.Manager, col);
                    def propName = col.Name; 
                    def fieldMemberName = $"_$(propName)";
                   
                    t.Define(<[decl: 
                        private mutable $(fieldMemberName : dyn) : $nemerleType; 
                    ]>);
                    
                    def isPk = pkeys.Contains(col.Name);
                    def isIdentity = col.AutoIncrement;

                    mutable attrs = [<[ MapFieldAttribute($(col.Name : string)) ]>];

                    when (isPk) 
                        attrs ::= <[ PrimaryKeyAttribute ]>;
                    
                    when (isIdentity) 
                        attrs ::= <[ IdentityAttribute]>;
                        
                    def attrsExprs = Modifiers(NemerleAttributes.Public, attrs);

                    t.Define(<[decl: 
                        ..$attrsExprs
                        $(propName : dyn) : $nemerleType
                        {
                            get { $(fieldMemberName : dyn); }
                            set { $(fieldMemberName : dyn) = value; }
                        }
                    ]>);
                    
                    _ = hintBuilder.Append($"$(propName) : $(nemerleType)\n");
                }
            }

            foreach (key in table.Keys.Where(t => !string.IsNullOrEmpty(t.RelTable)))
            {
              def typeName = DatabaseManagerImpl.GetTypeForTable(t, key.RelTable);
              def propType = PExpr.FromQualifiedIdentifier(t.Manager, typeName);

              if (propType != null)
              {
                def propName = typeName.Substring(typeName.LastIndexOf('.')+1);
                def fieldMemberName = $"_$(propName)";
                def keyName = $"$(key.Columns)_Id";

                t.Define(<[decl: 
                    private mutable $(fieldMemberName : dyn) : $propType; 
                ]>);
                t.Define(<[decl: 
                  [Association(ThisKey = $(keyName : string), OtherKey = $(key.RelColumns : string))] 
                  public $(propName : dyn) : $propType
                  {
                      get { $(fieldMemberName : dyn); }
                      private set { $(fieldMemberName : dyn) = value; }
                  }
                ]>);
                _ = hintBuilder.Append($"$propName : $typeName\n");
              }
              else
                _ = hintBuilder.Append($"can't find type for table $(key.RelTable)\n");
            }

            unless (hintBuilder.Length == 0)
                Message.Hint("\n" + hintBuilder.ToString())
          }
          catch 
          {
             | e => Message.Error($"$(t.Name) model error: $(e.ToString())");
          }
      }
      
      public ConvertToNemerleType(manager : ManagerClass, col : TableColumnSchema) : PExpr
      {
          def clrType = col.Type.Type;
          
          regexp match (clrType.FullName)
          {
              | @"^(?<type>.+)\[\]$" => def elem = PExpr.FromQualifiedIdentifier(manager, @type); <[ array[$elem] ]>;
              | _ => PExpr.FromQualifiedIdentifier(manager, clrType.FullName);
          }
      }
  }
}
